﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Lifetime;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
using Misuzilla.Crigate.Configuration;

namespace Misuzilla.Crigate.Handler
{
    public class HandlerLoader : MarshalByRefObject, IDisposable
    {
        private Dictionary<String, AppDomain> _appDomains;
        private List<IHandler> _handlers = new List<IHandler>();
        private Boolean _isDefaultDomain = false;
        
        public HandlerLoader()
        {
            _appDomains = new Dictionary<string, AppDomain>();
            _isDefaultDomain = AppDomain.CurrentDomain.IsDefaultAppDomain();
        }
        ~HandlerLoader()
        {
            Dispose();
        }
        
        public IHandler Activate(HandlerTypeInfo handlerTypeInfo)
        {
            Debug.Assert(AppDomain.CurrentDomain.IsDefaultAppDomain());

            AppDomain appDomain;
            if (handlerTypeInfo.IsRequireParentDomain)
            {
                // DefaultAppDomain でインスタンス化
                appDomain = AppDomain.CurrentDomain;
            }
            else if (handlerTypeInfo.IsRequireIsolatedDomain)
            {
                // 分離ドメインでインスタンス化
                appDomain = GetOrCreateDomain("Isolated."+DateTime.Now.Ticks);
            }
            else if (!String.IsNullOrEmpty(handlerTypeInfo.SharedAppDomainName))
            {
                // 共有ドメインでインスタンス化
                appDomain = GetOrCreateDomain("Shared."+handlerTypeInfo.SharedAppDomainName);
            }
            else
            {
                // 共通のハンドラ用ドメインでインスタンス化
                appDomain = GetOrCreateDomain("Neutral.Handler");
            }

            HandlerLoader handlerLoader = Activator.CreateInstance(appDomain,
                                                                   typeof (HandlerLoader).Assembly.FullName,
                                                                   typeof (HandlerLoader).FullName,
                                                                   false,
                                                                   BindingFlags.Instance | BindingFlags.Public,
                                                                   null,
                                                                   new object[] {},
                                                                   null,
                                                                   null,
                                                                   null).Unwrap() as HandlerLoader;
            return handlerLoader.CreateHandler(handlerTypeInfo);
        }

        public void ActivateHandlers(Session session, ICollection<HandlerTypeInfo> handlerTypeInfoList, ActivateContexts ctx)
        {
            Trace.WriteLine("ActivateHandlers");
            lock (_handlers)
            {
                foreach (var typeInfo in (from t in handlerTypeInfoList
                                          where t.IsAutoActivate && (t.ActivateContext == ctx)
                                          select t))
                {
                    if (session.IsStarted)
                        session.SendServerMessage(String.Format("* Activate Handler: {0}", typeInfo.Type));
                    var handler = Activate(typeInfo);
                    handler.Initialize(session);
                    AddHandler(handler);
                }

                ExecuteHandlers(handler =>
                                    {
                                        IHandlerEvents handlerEvents = handler as IHandlerEvents;
                                        if (handlerEvents != null)
                                            handlerEvents.OnHandlersInitialized();
                                        return true;
                                    });
            }
        }
        
        /// <summary>
        /// ハンドラを追加します。
        /// </summary>
        /// <param name="handler"></param>
        public void AddHandler(IHandler handler)
        {
            // TODO: Multi-domain
            lock (_handlers)
            {
                if (!_handlers.Contains(handler))
                    _handlers.Add(handler);
            }
        }

        /// <summary>
        /// ハンドラを削除します。
        /// </summary>
        /// <param name="handler"></param>
        public void RemoveHandler(IHandler handler)
        {
            // TODO: Multi-domain
            lock (_handlers)
            {
                if (_handlers.Contains(handler))
                    _handlers.Remove(handler);
            }
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        public Boolean ExecuteHandlers(Func<IHandler, Boolean> action)
        {
//            Trace.WriteLine("ExecuteHandlers");
            Boolean result = true;
            lock (_handlers)
            {
                IHandler[] handlers = new IHandler[_handlers.Count];
                Int32 removedCount = 0;
                _handlers.CopyTo(handlers, 0);
                for (var i = 0; i < handlers.Length; i++)
                {
                    IHandler target = handlers[i];
                    try
                    {
                        result &= action(target);
                    }
                    // ハンドラが無効になっているので削除する
                    catch (AppDomainUnloadedException)
                    {
                        _handlers.RemoveAt(i - removedCount);
                        removedCount++;
                    }
                    catch (TargetInvocationException targetInvocEx)
                    {
                        // ハンドラが無効になっているので削除する
                        if (targetInvocEx.InnerException is AppDomainUnloadedException)
                        {
                            _handlers.RemoveAt(i - removedCount);
                            removedCount++;
                        }
                        else if (targetInvocEx.InnerException != null)
                        {
                            throw targetInvocEx.InnerException;
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
            }
            return result;
        }

        // TODO: Multi-domain
        public T GetHandler<T>() where T : class, IHandler
        {
            lock (_handlers)
            {
                return _handlers.Find(handler => handler.GetType() == typeof (T)) as T;
            }
        }
    
        private AppDomain GetOrCreateDomain(String appDomainKey)
        {
            lock (_appDomains)
            {
                if (!_appDomains.ContainsKey(appDomainKey))
                {
                    AppDomainSetup appDomainSetup = new AppDomainSetup()
                                                        {
                                                            ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
                                                        };
                    AppDomain appDomain = AppDomain.CreateDomain("Crigate AppDomain-" + appDomainKey);
                    Trace.WriteLine(String.Format("AppDomain Created: {0} (ID:{1})", appDomain.FriendlyName, appDomain.Id));
                    appDomain.DoCallBack(() => {
                        LifetimeServices.LeaseTime = TimeSpan.Zero;
                        LifetimeServices.RenewOnCallTime = TimeSpan.Zero;
                    });
                    return _appDomains[appDomainKey] = appDomain;
                }
            }
            return _appDomains[appDomainKey];
        }

        
        #region IDisposable メンバ

        public void Dispose()
        {
            if (_isDefaultDomain)
            {
                lock (_handlers)
                {
                    ExecuteHandlers(handler =>
                                        {
                                            try
                                            {
                                                handler.Dispose();
                                            }
                                            catch
                                            {
                                            }
                                            return true;
                                        });
                }
                lock (_appDomains)
                {
                    foreach (var appDomain in _appDomains.Values)
                    {
                    Unload:
                        try
                        {
                            Trace.WriteLine(String.Format("AppDomain Unloading: {0} (ID:{1})", appDomain.FriendlyName,
                                                          appDomain.Id));
                            appDomain.UnhandledException += (sender, e) => { };
                            AppDomain.Unload(appDomain);
                        }
                        catch (Exception ex)//(CannotUnloadAppDomainException)
                        {
                            Trace.WriteLine("Cannot Unload AppDomain retry after 10sec");
                            Thread.Sleep(10 * 1000);
                            goto Unload;
                        }
                    }
                }
            }
        }

        #endregion
        
        public IHandler CreateHandler(HandlerTypeInfo handlerTypeInfo)
        {
            Trace.WriteLine(String.Format("HandlerLoader.CreateHandler: {0} (AppDomain:{1} (ID:{2}))", handlerTypeInfo.Type, AppDomain.CurrentDomain.FriendlyName, AppDomain.CurrentDomain.Id));
            return handlerTypeInfo.CreateInstance();
        }
    }
}
